<!--
 * @Author: error: error: git config user.name & please set dead value or install git && error: git config user.email & please set dead value or install git & please set dead value or install git
 * @Date: 2024-07-31 15:28:21
 * @LastEditors: error: error: git config user.name & please set dead value or install git && error: git config user.email & please set dead value or install git & please set dead value or install git
 * @LastEditTime: 2024-08-06 18:01:21
 * @FilePath: \flex-\4 canvas 数学基础.html
 * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
-->
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            list-style: none;
        }

        .box {
            width: 1000px;
            height: 800px;
            margin: 30px auto;
            display: flex;
            border: 1px solid;
        }

        #myCanvas {
            margin: auto;
            border: 1px solid;
        }
    </style>
</head>

<body>
    <div class="box">
        <canvas id="myCanvas" height="750" width="950" tabindex="333"></canvas>
    </div>
    <div id="txt">
        P点坐标（<input type="text" id="pointx">，<input type="text" id="pointy">）
    </div>
</body>
<script>
    const txt = document.getElementById("txt")
    const pointx = document.getElementById("pointx")
    const pointy = document.getElementById("pointy")
    let ids = null
    // 多边形
    let towerPoint = [
        { x: 100, y: 200.1 },
        { x: 200, y: 100 },
        // { x: 250, y: 180 },
        { x: 300, y: 100 },
        { x: 300, y: 200 }
    ]
    let test = [
        { x: 0, y: 4 },
        { x: 4, y: 0 }
    ]
    /* * @type { HTMLCanvasElement } */
    const myCanvas = document.getElementById('myCanvas');
    const ctx = myCanvas.getContext('2d');

    txt.onchange = (e) => {
        ctx.clearRect(0, 0, myCanvas.width, myCanvas.height)
        let point = {
            x: pointx.value,
            y: pointy.value,
        }
        drawPolygon(ctx, towerPoint)
        drawPoint(ctx, point)
        console.log(isInMultiPolygon(towerPoint, point));
    }
    myCanvas.onclick = (e) => {
        ctx.clearRect(0, 0, myCanvas.width, myCanvas.height)
        let point = {
            x: e.offsetX,
            y: e.offsetY,
        }
        pointx.value = e.offsetX
        pointy.value = e.offsetY
        drawPolygon(ctx, towerPoint)
        drawPoint(ctx, point)
        console.log(isInMultiPolygon(towerPoint, point));
    }

    // 画多边形，并且求取凸、凹多边形面积 
    function drawPolygon(ctx, pointArr, needDraw = true) {
        if (!pointArr || pointArr.length < 2) {
            return null;
        }
        if (needDraw) {
            if (pointArr.length === 2) {
                ctx.save();
                ctx.beginPath();
                ctx.moveTo(pointArr[0].x, pointArr[0].y)
                ctx.lineTo(pointArr[1].x, pointArr[1].y)
                ctx.stroke()
                ctx.closePath();
                ctx.restore();
                return null;
            }
            for (let i = 0; i < pointArr.length; i++) {
                ctx.save();
                ctx.beginPath();
                let num = i + 1
                if (num >= pointArr.length) {
                    num = 0
                }
                ctx.moveTo(pointArr[i].x, pointArr[i].y)
                ctx.lineTo(pointArr[num].x, pointArr[num].y)
                ctx.stroke()
                ctx.closePath();
                ctx.restore();
            }
        }
        /* 求取面积 */
        if (pointArr.length == 2) {
            return {
                realArea: 0, // 真实面积
                absArea: 0,
            }
        }
        let squareArea = 0
        let absArea = 0
        let basePoint = pointArr[0]
        // 转化为相对于起点的各处向量
        let newPointArr = changePointToBase(pointArr[0], pointArr)
        for (let i = 1; i < newPointArr.length; i++) {
            let y = i + 1
            if (y >= newPointArr.length - 1) {
                y = newPointArr.length - 1
            }
            squareArea += cross2Product(newPointArr[i], newPointArr[y])
            absArea += Math.abs(cross2Product(newPointArr[i], newPointArr[y]))
        }
        return {
            realArea: Math.abs(squareArea), // 真实面积
            absArea: absArea,   // 绝对值面积
        };
    }

    // 计算几何中心--->图形控制点
    function calcPolyCenterPoint(pointArr) {
        if (!pointArr) return null;
        if (pointArr.length === 1) return pointArr[0];
        let xSum = 0
        let ySum = 0
        pointArr.forEach(it => {
            xSum += it.x
            ySum += it.y
        });
        return {
            x: xSum / pointArr.length,
            y: ySum / pointArr.length,
        }
    }

    // 画点
    function drawPoint(ctx, point) {
        ctx.save();
        ctx.beginPath();
        ctx.beginPath()
        ctx.arc(point.x, point.y, 3, 0, 2 * Math.PI)
        ctx.closePath()
        ctx.stroke()
        ctx.closePath();
        ctx.restore();
    }

    // 二维向量叉乘的模/三角形面积（以原点为起点）
    function cross2Product(point1, point2, needTriSquare = true) {
        // needTriSquare 为 true 是求三角形面积
        // needTriSquare 为 false 是求叉乘的模
        return needTriSquare ? 0.5 * (point1.x * point2.y - point1.y * point2.x) : point1.x * point2.y - point1.y * point2.x
    }

    // 判断凸、凹多边形
    // true 凸，反之为凹
    function isConvexOrConcave(pointArr) {
        if (!pointArr) {
            throw new Error("请输入合法参数")
        }
        if (pointArr.length === 1) {
            throw new Error("输入图形是点")
        }
        if (pointArr.length === 2) {
            throw new Error("输入图形是线段")
        }
        let sideSize = pointArr.length; // 输入图形的边数
        let normalAngelSum = sideSize * 180 - 360   // 正常凸多边形的时候的内角和
        let currentAngelSum = 0
        for (let i = 0; i < pointArr.length; i++) {
            let y = i + 1;
            if (i === 0) {
                currentAngelSum = currentAngelSum + mutipleProduct(changePointToBase(pointArr[0], [pointArr[pointArr.length - 1]])[0], changePointToBase(pointArr[0], [pointArr[1]])[0]).angel
            } else {
                currentAngelSum = currentAngelSum + mutipleProduct(changePointToBase(pointArr[i], [pointArr[i - 1]])[0], changePointToBase(pointArr[i], [pointArr[y]])[0]).angel
            }
            if (y >= pointArr.length - 1) {
                currentAngelSum = currentAngelSum + mutipleProduct(changePointToBase(pointArr[y], [pointArr[0]])[0], changePointToBase(pointArr[y], [pointArr[i]])[0]).angel
                break;
            }
        }
        if (Number.isNaN(currentAngelSum)) {
            throw new Error("输入点有重复！")
        }
        // console.log(currentAngelSum.toFixed(2));
        return currentAngelSum.toFixed(2) === normalAngelSum.toFixed(2);    // 校准精度
    }

    // 求向量点乘，内夹角
    function mutipleProduct(point1, point2) {
        if (!point1 || !point2) {
            return {
                multiNum: 0,
                angel: 0
            }
        };
        let point1Model = Math.sqrt(point1.x * point1.x + point1.y * point1.y)
        let point2Model = Math.sqrt(point2.x * point2.x + point2.y * point2.y)
        return {
            multiNum: point1.x * point2.x + point1.y * point2.y,    // 点积的值
            angel: Math.acos((point1.x * point2.x + point1.y * point2.y) / (point1Model * point2Model)) * 180 / Math.PI,    // 两向量所成夹角（角度）
        };
    }

    // 计算根据基准点(basePoint)为原点的基础坐标系
    function changePointToBase(basePoint, otherPoint) {
        return otherPoint.map(point => {
            return {
                x: point.x - basePoint.x,
                y: point.y - basePoint.y
            }
        })
    }

    // 判断点是否在凸多边形内
    // true 在内部
    // false 在外部
    // '边' 在边界
    function isInMultiPolygon(pointArr, point) {
        // 首先判断图形是凸的还是凹的
        if (!isConvexOrConcave(pointArr)) {
            throw new Error("当前图形是凹多边形，该函数无法判别！")
            return false;
        }
        let newPointArr = changePointToBase(point, pointArr)
        let basePointAbsArea = 0    // 以基准点为坐标系，与图形的各个顶点相连分割的三角形相加的面积
        // console.log('原多边形的面积 :>> ', drawPolygon(null, pointArr, false));
        for (let i = 0; i < newPointArr.length; i++) {
            let y = i + 1
            if (y === newPointArr.length) {
                y = 0
            }
            if (cross2Product(newPointArr[i], newPointArr[y]) === 0) {
                return '边';
            }
            basePointAbsArea += Math.abs(cross2Product(newPointArr[i], newPointArr[y]))
        }
        return basePointAbsArea.toFixed(3) === drawPolygon(null, pointArr, false).realArea.toFixed(3)   // 校准精度
    }

    drawPolygon(ctx, towerPoint)
    pointx.value = calcPolyCenterPoint(towerPoint).x
    pointy.value = calcPolyCenterPoint(towerPoint).y
    let pointMouse = {
        x: calcPolyCenterPoint(towerPoint).x,
        y: calcPolyCenterPoint(towerPoint).y
    }
    drawPoint(ctx, pointMouse)
    isInMultiPolygon(towerPoint, pointMouse)


</script>

</html>